package network.protocol;
import game.engine.GSync;
import game.objects.GameObject;
import graphics.objects.GraphicsObject;

import java.util.Vector;

import physics.Body;
import physics.joints.Joint;
import tools.Help;
import tools.Log;
import tools.Profiler;
import tools.id.IdSystem;

import network.NetworkConstants;
import network.RSocket;
import network.byteConversions.ByteConverter;
import network.dataFormats.GroToGO;
import network.dataFormats.NetworkData;
import network.dataFormats.NetworkPackage;
import network.dataFormats.PackageType;
import network.serializing.SerializeInit;
import network.socket.TCPServer;
import network.synchronizing.SyncDataCreator;
/**
 * Keeps a list of clients, initializes them upon adding, keeps them up to date with engine synchronization ...
 * 
 * @author toni
 * 
 */
public class ServerHandler extends NetworkHandler {
	private TCPServer serverSocket;
	private IdSystem<VirtualClient> clients;
	private long lastSyncTime;
	public ServerHandler() {
		super();
		clients = new IdSystem<VirtualClient>(1);
		id = 0; // Server has the id 0
	}
	@Override
	public void handle() {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			Vector<NetworkData> outgoingSecure = makeNetworkData(createData(c));
			Vector<NetworkData> outgoingFast = makeNetworkDataFast(createSyncData(c));
			Vector<NetworkData> incoming = c.popData();
			setProfilerData(outgoingSecure, outgoingFast, incoming);
			c.sendData(outgoingSecure);
			c.sendUnsecure(outgoingFast);
			processData(c, incoming);
		}
		addNewClients();
	}
	private void addNewClients() {
		if (serverSocket != null) {
			for (int cI : serverSocket.getNewClients()) {
				addClient(cI);
				// clients.add(new VirtualClient(new ServerSocket(serverSocket,
				// cI)));
			}
		}
	}
	private void processData(VirtualClient c, Vector<NetworkData> incoming) {
		Vector<NetworkPackage> packages = makeNetworkPackages(incoming);
		for (NetworkPackage p : packages) {
			switch (p.header.type) {
				case REQUESTINIT :
					sendInitialData(c);
					break;
				case GOTINIT :
					c.initialized = true;
					break;
				case CONTROL :
					handleControl(p);
			}
		}
	}
	private void handleControl(NetworkPackage p) {
		// First four bytes : The id
		int idFrom = ByteConverter.bytesToInt(p.getData(), 0);
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			int id = c.getId();
			if (idFrom != id) {
				c.addControlData(p);
			}
		}
		game.controlHandling.handleControlData(p);
	}
	private Vector<NetworkPackage> createData(VirtualClient c) {
		// Client has not even been sent the initial game state? Don't send him
		// any current information.
		if (!c.initialized){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return new Vector<NetworkPackage>();
		}
		Vector<NetworkPackage> packages = new Vector<NetworkPackage>();
		// Add missing and remove redundant objects
		for (Body b : c.bodiesToAdd) {
			packages.add(PackageCreator.addBodyPackage(b, getNextId(), game.time));
		}
		for (Body b : c.bodiesToRem) {
			packages.add(PackageCreator.remBodyPackage(b, getNextId(), game.time));
		}
		for (Joint b : c.jointsToAdd) {
			packages.add(PackageCreator.addJointPackage(b, getNextId(), game.time));
		}
		for (Joint b : c.jointsToRem) {
			packages.add(PackageCreator.remJointPackage(b, getNextId(), game.time));
		}
		for (GraphicsObject b : c.graphicsObjectsToAdd) {
			packages.add(PackageCreator.addGraphicsObjectPackage(b, getNextId(), game.time));
		}
		for (GraphicsObject b : c.graphicsObjectsToRem) {
			packages.add(PackageCreator.remGraphicsObjectPackage(b, getNextId(), game.time));
		}
		for (GameObject b : c.gameObjectsToAdd)
			packages.add(PackageCreator.addGameObjectPackage(b, getNextId(), game.time));
		for (GameObject b : c.gameObjectsToRem)
			packages.add(PackageCreator.remGameObjectPackage(b, getNextId(), game.time));
		for (int[] twoIds : c.bodiesToGos) {
			packages.add(PackageCreator.addBodyToGameObjectPackage(twoIds[0], twoIds[1], getNextId(), game.time));
		}
		for (int[] twoIds : c.jointsToGos) {
			packages.add(PackageCreator.addJointToGameObjectPackage(twoIds[0], twoIds[1], getNextId(), game.time));
		}
		// GroToGo contains the ids of the gameObject and the graphics object as
		// well as the GSync for this graphics object.
		for (GroToGO data : c.graphicsObjectsToGos) {
			packages.add(PackageCreator.addGraphicsObjectToGameObjectPackage(data, getNextId(), game.time));
		}
		for (NetworkPackage p : c.controlData) {
			packages.add(p);
		}
		c.bodiesToAdd.clear();
		c.bodiesToRem.clear();
		c.bodiesToGos.clear();
		c.jointsToAdd.clear();
		c.jointsToRem.clear();
		c.jointsToGos.clear();
		c.graphicsObjectsToAdd.clear();
		c.graphicsObjectsToRem.clear();
		c.graphicsObjectsToGos.clear();
		c.gameObjectsToAdd.clear();
		c.gameObjectsToRem.clear();
		c.controlData.clear();
		return packages;
	}
	private void createControlData(Vector<NetworkPackage> packages) {
		NetworkPackage p = game.controlHandling.getControlData(getNextId(), game.time);
		if (p != null)
			packages.add(p);
	}
	private Vector<NetworkPackage> createSyncData(VirtualClient c) {
		// Synchronize objects
		Vector<NetworkPackage> packages = new Vector<NetworkPackage>();
		SyncDataCreator.synchronize(packages, game, this);
		syncTime(packages, getCurrentFps(), getNextId(), game.time);
		createControlData(packages);
		return packages;
	}
	private void syncTime(Vector<NetworkPackage> packages, int currentFps, int nextId, float time) {
		if (NetworkConstants.DOTIMESYNC) {
			if (System.currentTimeMillis() - lastSyncTime > NetworkConstants.TIMESYNCINTERVAL) {
				lastSyncTime = System.currentTimeMillis();
				packages.add(PackageCreator.timeSync(currentFps, id, time));
			}
		}
	}
	private void sendInitialData(VirtualClient client) {
		// Be sure we dont send this package too often, it can be extremely
		// large
		if (client.initialized || System.currentTimeMillis() - client.lastTimeInitSend < NetworkConstants.INITINTERVAL) {
			return;
		}
		Log.n("Server sending initial data to new client");
		client.lastTimeInitSend = System.currentTimeMillis();
		NetworkInit init = new NetworkInit(game, client);
		byte[] data = SerializeInit.toData(init);
		Vector<NetworkPackage> packageVector = new Vector<NetworkPackage>();
		packageVector.add(new NetworkPackage(PackageType.INIT, data, getNextId(), game.time));
		Vector<NetworkData> initData = makeNetworkData(packageVector);
		client.sendData(initData);
	}
	public void addClient(int cid) {
		VirtualClient newClient = new VirtualClient(serverSocket.getClientSocket(cid));
		clients.add(newClient);
		game.addPlayer(newClient.getId());
		// UGLY
		if (serverSocket == null)
			sendInitialData(newClient); // This is only needed in non-multithreaded tester version. The correct version is built in aswell
	}
	public void remClient(VirtualClient c) {
		clients.remove(c);
	}
	@Override
	public void addBody(GameObject go, Body b) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.addBody(b, go);
		}
	}
	@Override
	public void remBody(GameObject go, Body b) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.remBody(b, go);
		}
	}
	@Override
	public void addJoint(GameObject go, Joint j) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.addJoint(j, go);
		}
	}
	@Override
	public void remJoint(GameObject go, Joint j) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.remJoint(j, go);
		}
	}
	@Override
	public void addGraphicsObject(GameObject go, GSync s, GraphicsObject o) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.addGraphicsObject(o, s, go);
		}
	}
	@Override
	public void remGraphicsObject(GameObject go, GraphicsObject o) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.remGraphicsObject(o, go);
		}
	}
	@Override
	public void addGameObject(GameObject go) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.addGameObject(go);
		}
	}
	@Override
	public void remGameObject(GameObject go) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			c.remGameObject(go);
		}
	}
	public void setServerSocket(TCPServer serverSocket) {
		this.serverSocket = serverSocket;
	}
	public TCPServer getServerSocket() {
		return serverSocket;
	}
	@Override
	public void addPlayer(int playerId) {
		for (int i = 0; i < clients.size(); i++) {
			VirtualClient c = clients.get(i);
			NetworkPackage p = new NetworkPackage(PackageType.ADDP, ByteConverter.intToBytes(playerId), getNextId(), game.time);
			sendSinglePackage(c, p);
		}
	}
	private void sendSinglePackage(VirtualClient c, NetworkPackage p) {
		NetworkData d = new NetworkData(p);
		Vector<NetworkData> all = new Vector<NetworkData>();
		all.add(d);
		c.sendData(all);
	}
	private void setProfilerData(Vector<NetworkData> outgoingSecure, Vector<NetworkData> outgoingFast, Vector<NetworkData> incoming) {
		/*
		 * int size = 0; int num = 0; for (NetworkData d : outgoingSecure) { size += d.data.length; num++; } for (NetworkData d : outgoingFast) { size += d.data.length; num++; }
		 */
	}
}
